🎯 核心思想
Metona 面向多 LLM Provider,每个外部 API 的请求/响应格式各不相同(OpenAI 格式、Anthropic 格式、Ollama 原生格式等)。 如果项目各处代码直接依赖外部格式,切换 Provider 或新增模型将导致大规模改动。
Metona IR 在项目内部建立一道抽象边界:
┌──────────────────────────┐ │ Agent Loop / IPC / UI │ ← 只读写 Metona IR └────────────┬─────────────┘ │ ┌────────────▼─────────────┐ │ Metona IR Standard │ ← 项目内唯一标准 └────────────┬─────────────┘ │ ┌────────┼────────┐ │ │ │ ┌───▼──┐ ┌──▼───┐ ┌─▼───┐ │DeepSeek│ │Agnes│ │Ollama│ ← Adapter 层 └───────┘ └─────┘ └──────┘
铁律:electron/harness/ 下的所有代码、src/ 下的所有 UI 代码、IPC 通道传输的数据,只能使用 Metona IR 定义的类型。
任何外部 API 的原始类型不得穿透到这些层。
🏗️ 架构概览
一次完整的用户请求经过以下数据流转:
1. UI (Renderer) │ 构造 MetonaRequest,通过 IPC 发送到主进程 │ 2. IPC Bridge │ 传输 MetonaRequest JSON │ 3. Context Builder │ 注入 System Prompt、会话历史、检索记忆、可用工具列表 │ 输出 MetonaContext │ 4. Provider Adapter │ 将 MetonaContext 转换为目标 Provider 的原生请求格式 │ 调用外部 API │ 将原生响应转换为 MetonaResponse / MetonaStreamEvent │ 5. Agent Loop Engine │ 按 ReAct 状态机解析 MetonaResponse │ 提取 Thought → 执行 ToolCall → 收集 Observation │ 构建下一轮的 MetonaRequest │ 6. IPC → UI │ 将 MetonaStreamEvent / MetonaResponse 推送回渲染进程
📤 请求标准:MetonaRequest
Agent Loop 向 Provider Adapter 发出的统一请求。每次 ReAct 迭代构造一个新的 MetonaRequest。
完整类型定义
// ====== electron/harness/types/metona-request.ts ====== export interface MetonaRequest { /** 请求元信息 */ meta: MetonaRequestMeta; /** System Prompt(行为宪法) */ systemPrompt: MetonaSystemPrompt; /** 消息列表(含历史 + 当前用户输入 + 工具结果) */ messages: MetonaMessage[]; /** 本轮可用的工具定义列表 */ tools?: MetonaToolDef[]; /** 生成参数 */ params: MetonaGenerationParams; /** 安全约束 */ constraints?: MetonaConstraints; } export interface MetonaRequestMeta { sessionId: string; // 会话 ID iteration: number; // 当前 ReAct 迭代轮次(从 1 开始) requestId: string; // 本次请求的唯一 ID timestamp: number; // Unix 毫秒时间戳 agentVersion: string; // Agent 引擎版本 } export interface MetonaSystemPrompt { /** 角色定义(静态区,利用 LLM 缓存) */ roleDefinition: string; /** 输出格式约束 */ outputConstraints: string; /** 安全准则 */ safetyGuidelines: string; /** 动态注入的尾部提醒 */ dynamicReminders?: string; } export interface MetonaGenerationParams { maxTokens?: number; // 最大生成 token 数 temperature?: number; // 温度(默认 0.0,Agent 需要确定性) topP?: number; // 核采样 stream?: boolean; // 是否流式输出 stopSequences?: string[]; // 停止序列 thinkingEnabled?: boolean; // 是否启用思考模式 thinkingEffort?: 'low' | 'medium' | 'high' | 'max'; // 思考强度(替代 thinkingBudget,各 Provider 映射见 Adapter 规范) } export interface MetonaConstraints { allowedTools?: string[]; // 本迭代允许使用的工具白名单 maxToolCalls?: number; // 单轮最大工具调用数 timeoutMs?: number; // 本请求整体超时 }
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| meta | MetonaRequestMeta | 必填 | 请求元信息,含 sessionId、iteration、requestId、timestamp |
| systemPrompt | MetonaSystemPrompt | 必填 | 分区化的 System Prompt,Adaper 负责拼接为 Provider 格式 |
| messages | MetonaMessage[] | 必填 | 统一消息列表,见下方消息格式 |
| tools | MetonaToolDef[] | 可选 | 本轮可用工具列表 |
| params | MetonaGenerationParams | 必填 | 生成参数 |
| constraints | MetonaConstraints | 可选 | 安全约束和限流参数 |
💬 消息格式:MetonaMessage
Metona 统一消息结构。所有角色(system / user / assistant / tool)共用同一结构,通过 role 区分。
export interface MetonaMessage { role: 'system' | 'user' | 'assistant' | 'tool'; /** 文本内容(纯文本或 Markdown) */ content: string; /** (仅 assistant)思考/推理内容 */ reasoningContent?: string; /** (仅 assistant)工具调用请求 */ toolCalls?: MetonaToolCall[]; /** (仅 tool)工具执行结果 */ toolResult?: MetonaToolResult; /** 时间戳 */ timestamp: number; /** 所属迭代轮次 */ iteration?: number; /** 图片内容(可选,用于多模态) */ images?: MetonaImageContent[]; } export interface MetonaImageContent { url: string; // 图片公网 URL 或 base64 data URI detail?: 'low' | 'high' | 'auto'; }
reasoning_content 需要伴随 toolCalls 回传上下文。
MetonaMessage 统一携带 reasoningContent,由 Adapter 决定是否需要回传。
MetonaMessage.content(string)+ images(数组)的分离设计比 OpenAI 的 content 数组更清晰。Adapter 负责转换:
• DeepSeek/Agnes (OpenAI 兼容):
content 数组 = [{type:"text", text: content}, ...images.map(i => ({type:"image_url", image_url:{url: i.url}}))]
• Ollama:
content 保持 string,images 作为消息的独立字段传入 base64 数组(Adapter 需将 URL 下载为 base64)
• 纯文本消息(无 images):所有 Provider 的
content 直接传 string
🔧 工具定义:MetonaToolDef
统一的工具描述格式。内置工具和 MCP 动态工具都使用此结构。
export interface MetonaToolDef { name: string; // 工具唯一名称 description: string; // 功能描述(供 LLM 阅读) parameters: MetonaToolParams; // 参数 JSON Schema category: MetonaToolCategory; // 分类 riskLevel: MetonaRiskLevel; // 风险等级 requiresPermission: boolean; // 是否需要用户授权 timeoutMs: number; // 超时时间 } export interface MetonaToolParams { type: 'object'; properties: Record<string, MetonaParamField>; required?: string[]; } export interface MetonaParamField { type: 'string' | 'number' | 'boolean' | 'object' | 'array'; description: string; enum?: string[]; items?: MetonaParamField; } export enum MetonaToolCategory { FILESYSTEM = 'filesystem', SEARCH = 'search', CALCULATION = 'calculation', CODE_EXECUTION = 'code_execution', NETWORK = 'network', DATABASE = 'database', MCP = 'mcp', CUSTOM = 'custom', } export enum MetonaRiskLevel { SAFE = 'safe', LOW = 'low', MEDIUM = 'medium', HIGH = 'high', CRITICAL = 'critical', }
MetonaToolDef 与内部 ToolDefinition 的关系
项目内部存在两种工具类型:MetonaToolDef(IR 标准类型)和 ToolDefinition(内部实现类型,使用 Zod Schema 进行运行时参数校验)。两者的关系如下:
| 维度 | MetonaToolDef(IR 标准) | ToolDefinition(内部实现) |
|---|---|---|
| 用途 | 跨进程传输、LLM 可读描述、IPC 通信 | 运行时参数校验、工具注册、安全检查 |
| 参数格式 | JSON Schema(MetonaToolParams) | Zod Schema(支持类型推断和运行时校验) |
| 定义位置 | 本文档(IR 标准) | electron/harness/tools/base-tool.ts |
BaseTool.getDescriptionForLLM() 负责将 Zod Schema → JSON Schema → MetonaToolDef。
•
ToolDefinition.name → MetonaToolDef.name
•
ToolDefinition.parameters(Zod)→ MetonaToolDef.parameters(JSON Schema),使用 zod-to-json-schema 库转换
•
ToolDefinition.category → MetonaToolDef.category(枚举值一致)
•
ToolDefinition.riskLevel → MetonaToolDef.riskLevel(枚举值一致)
• IPC 通道和 Agent Loop 只使用 MetonaToolDef,不接触 ToolDefinition/Zod
• 工具注册表(
ToolRegistry)内部使用 IBaseTool,对外暴露时转换为 MetonaToolDef
MetonaToolDef 格式为准(JSON Schema),不再使用自然语言描述。内部实现时使用 Zod Schema 做运行时校验,通过 zod-to-json-schema 转换后对外暴露。
📥 响应标准:MetonaResponse
Provider Adapter 将外部 API 的原始响应转换为 MetonaResponse 后返回给 Agent Loop。
export interface MetonaResponse { /** 响应元信息 */ meta: MetonaResponseMeta; /** 模型输出(完整文本) */ content: string; /** 思考/推理内容(Thinking 模式) */ reasoningContent?: string; /** 结构化输出(如果模型原生支持 JSON Schema) */ structuredOutput?: unknown; /** 工具调用请求列表 */ toolCalls?: MetonaToolCall[]; /** Token 使用统计 */ usage: MetonaTokenUsage; /** 停止原因 */ finishReason: MetonaFinishReason; /** 错误信息(如果出错) */ error?: MetonaError; } export interface MetonaResponseMeta { requestId: string; // 对应的请求 ID provider: string; // Provider 标识(如 'deepseek') model: string; // 实际使用的模型名称 latencyMs: number; // 端到端延迟 timestamp: number; // 响应时间戳 /** Provider 原生性能统计(可选,主要用于 Ollama) */ perfStats?: { loadDurationMs?: number; // 模型加载耗时 promptEvalDurationMs?: number; // Prompt 评估耗时 evalDurationMs?: number; // 生成耗时 tokensPerSecond?: number; // 生成速率 }; } export interface MetonaTokenUsage { inputTokens: number; outputTokens: number; totalTokens: number; reasoningTokens?: number; // Thinking 模式专用 cacheHitTokens?: number; cacheMissTokens?: number; } export enum MetonaFinishReason { STOP = 'stop', // 自然结束 LENGTH = 'length', // 达到长度上限 TOOL_CALLS = 'tool_calls', // 因工具调用而停止 CONTENT_FILTER = 'content_filter', // 内容过滤 ERROR = 'error', // 错误终止 }
⚡ 流式响应:MetonaStreamEvent
流式输出使用统一的事件类型,Agent Loop 和 UI 均可订阅。
/** 流式事件类型枚举 */ export enum MetonaStreamEventType { TEXT_DELTA = 'text_delta', // 文本增量 REASONING_DELTA = 'reasoning_delta', // 推理内容增量 TOOL_CALL_DELTA = 'tool_call_delta', // 工具调用增量 TOOL_CALL_COMPLETE = 'tool_call_complete', THINKING_START = 'thinking_start', // 思考开始 THINKING_END = 'thinking_end', // 思考结束 ERROR = 'error', // 流中错误 DONE = 'done', // 流结束 USAGE = 'usage', // Token 统计(通常在 DONE 前) } export interface MetonaStreamEvent { type: MetonaStreamEventType; requestId: string; sessionId: string; iteration: number; seq: number; // 序列号 timestamp: number; /** 根据 type 使用不同字段 */ delta?: string; // TEXT_DELTA / REASONING_DELTA toolCallDelta?: { index: number; // 工具调用索引(同一轮可能有多个) name?: string; // 工具名称片段(首个事件携带) argsDelta?: string; // 参数 JSON 增量片段 }; // TOOL_CALL_DELTA toolCall?: MetonaToolCall; // TOOL_CALL_COMPLETE(拼接完成后的完整调用) usage?: MetonaTokenUsage; // USAGE error?: MetonaError; // ERROR }
流式传输协议
IPC 通道使用 SSE-like 格式(Server-Sent Events),每条事件为一行 JSON:
// 实际传输格式(IPC 通道内,每行一条事件)
{"type":"thinking_start","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":0,"timestamp":1719000000000}
{"type":"reasoning_delta","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":1,"timestamp":1719000000100,"delta":"让我先分析问题的关键点..."}
{"type":"thinking_end","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":2,"timestamp":1719000000500}
{"type":"text_delta","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":3,"timestamp":1719000000600,"delta":"根据分析,"}
{"type":"text_delta","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":4,"timestamp":1719000000650,"delta":"答案是..."}
{"type":"tool_call_complete","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":5,"timestamp":1719000000700,"toolCall":{...}}
{"type":"usage","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":6,"timestamp":1719000000800,"usage":{"inputTokens":120,"outputTokens":45,"totalTokens":165}}
{"type":"done","requestId":"r_1","sessionId":"s_1","iteration":1,"seq":7,"timestamp":1719000000800}
流式工具调用拼接策略
不同 Provider 的流式工具调用返回方式不同,Adapter 负责统一处理:
| Provider | 流式工具调用格式 | Adapter 处理方式 |
|---|---|---|
| DeepSeek / Agnes | 分片返回:delta.tool_calls[i] 先返回 index + function.name 片段,再返回 function.arguments 片段 |
Adapter 缓冲拼接:每个 delta.tool_calls 片段转换为 TOOL_CALL_DELTA 事件;拼接完成后发 TOOL_CALL_COMPLETE |
| Ollama | 整块返回:message.tool_calls 在最后一个 chunk 中一次性返回 |
Adapter 直接转换为 TOOL_CALL_COMPLETE 事件(无 TOOL_CALL_DELTA) |
Map<number, {name: string, argsBuffer: string}> 缓冲区。
• 收到
TOOL_CALL_DELTA 时:如果 name 不为空,初始化缓冲区;将 argsDelta 追加到 argsBuffer
• 收到流结束(
done)或 finish_reason: "tool_calls" 时:遍历缓冲区,对每个拼接完整的工具调用发 TOOL_CALL_COMPLETE 事件(JSON.parse(argsBuffer) 作为 args)
• UI 端可以选择忽略
TOOL_CALL_DELTA 事件,只监听 TOOL_CALL_COMPLETE(简化实现)
🧠 思考内容:MetonaThinking
各 Provider 的思考/推理内容格式不同(DeepSeek 用 reasoning_content、Anthropic 用 thinking block、Ollama 在 think 标签内),统一为 MetonaThinking。
export interface MetonaThinking { /** 思考内容文本 */ content: string; /** 思考状态 */ status: 'thinking' | 'complete'; /** 思考耗时 (ms) */ durationMs: number; /** 思考消耗的 token 数 */ tokensUsed: number; }
thinking_start 事件开始,一系列 reasoning_delta 传输增量,最后 thinking_end 结束。
最终在 MetonaResponse 中合并为完整的 reasoningContent 字符串。
🔌 工具调用:MetonaToolCall
export interface MetonaToolCall { id: string; // 工具调用唯一 ID name: string; // 工具名称 args: Record<string, unknown>; // 调用参数 iteration: number; // 所属 ReAct 迭代 timestamp: number; } export interface MetonaToolResult { toolCallId: string; toolName: string; result: unknown; // 工具原始返回值 summary?: string; // 人工可读摘要(用于 LLM 上下文注入) success: boolean; error?: string; durationMs: number; timestamp: number; }
工具结果注入规则:工具执行完毕后,构造 role='tool' 的 MetonaMessage 追加到 messages 数组中。
summary 字段供 LLM 阅读(精简后),result 保留原始值供审计和调试。
MetonaRequest.tools 中传入工具列表,LLM 原生返回 tool_calls 结构化数据。Adapter 直接映射为 MetonaResponse.toolCalls,无需文本解析。
• DeepSeek:请求参数
tools + tool_choice,响应 choices[0].message.tool_calls
• Agnes:同 DeepSeek(OpenAI 兼容)
• Ollama:请求参数
tools,响应 message.tool_calls
三个 Provider 均原生支持 Tool Calling,正则解析仅作为 Provider 不支持时的降级方案。
1. Tool Calling(首选):利用 Provider 原生的
tools + tool_choice 参数,LLM 直接返回结构化 tool_calls。
2. Structured Output(备选):当不需要工具调用但需要结构化输出时,使用
response_format: json_object。
3. 正则降级(仅兜底):仅在 Provider 不支持 Tool Calling 时使用(当前三个 Provider 全部支持,实际不触发)。
❌ 错误格式:MetonaError
export interface MetonaError { code: MetonaErrorCode; // 错误码 message: string; // 人类可读的错误描述 provider?: string; // 出错的 Provider providerCode?: string; // Provider 原始错误码 retryable: boolean; // 是否可重试 retryAfterMs?: number; // 建议重试等待时间 } export enum MetonaErrorCode { // 网络层 NETWORK_TIMEOUT = 'network_timeout', NETWORK_ERROR = 'network_error', // 认证层 AUTH_INVALID = 'auth_invalid', AUTH_EXPIRED = 'auth_expired', // 频率限制 RATE_LIMITED = 'rate_limited', QUOTA_EXCEEDED = 'quota_exceeded', // 模型层 MODEL_OVERLOADED = 'model_overloaded', MODEL_NOT_FOUND = 'model_not_found', CONTEXT_LENGTH_EXCEEDED = 'context_length_exceeded', OUTPUT_LENGTH_EXCEEDED = 'output_length_exceeded', // 内容层 CONTENT_FILTERED = 'content_filtered', // 解析层 PARSE_ERROR = 'parse_error', INVALID_RESPONSE = 'invalid_response', // Agent 层 MAX_ITERATIONS = 'max_iterations', USER_ABORTED = 'user_aborted', TIMEOUT = 'timeout', UNKNOWN = 'unknown', }
📋 上下文标准:MetonaContext
Context Builder 的输出,包含了完整的上下文信息,供 Agent Loop 和 Adapter 使用。
export interface MetonaContext { /** 上下文唯一标识 */ id: string; /** 关联的会话 */ sessionId: string; /** System Prompt 分区 */ systemPrompt: MetonaSystemPrompt; /** 会话历史(最近 N 轮) */ history: MetonaMessage[]; /** 检索到的相关记忆 */ relevantMemories: MetonaMemoryItem[]; /** 当前任务信息 */ currentTask: { userInput: string; iteration: number; taskGoal?: string; }; /** 可用工具列表 */ availableTools: MetonaToolDef[]; /** 预估 Token 数 */ estimatedTokens: number; /** 上下文使用率(estimatedTokens / contextWindow) */ usageRatio: number; /** 是否需要压缩 */ needsCompression: boolean; }
当
usageRatio > 0.8 时 needsCompression = true,Agent Loop 进入 COMPRESSING 状态。
压缩流程(由 ContextBuilder 负责):
1. 保留最近 N 轮对话原文(N 由配置决定,默认 5)
2. 将更早的对话轮次用 LLM 摘要为一组 "对话摘要" 消息插入上下文
3. 保留所有
tool_calls 和 tool_results 的精简版(只保留工具名 + 结果状态,省略完整输出)
4. 保留 System Prompt 和 MEMORY.md 注入内容不变
5. 压缩后重新计算
estimatedTokens,确保 usageRatio < 0.5
注意区分:上下文压缩(压缩 LLM 对话窗口)≠ 记忆压缩(清理 SQLite 记忆库),两者由不同组件负责。
🧩 记忆格式:MetonaMemoryItem
export interface MetonaMemoryItem { id: string; type: 'episodic' | 'semantic' | 'working'; /** 可被 LLM 阅读的记忆内容 */ content: string; /** 精简摘要(上下文紧张时使用) */ summary?: string; /** 来源 */ source: 'user_input' | 'tool_result' | 'agent_thought' | 'imported'; /** 重要程度 0-1 */ importance: number; /** 检索相关性分数(仅在检索结果中出现) */ relevanceScore?: number; sessionId?: string; createdAt: number; expiresAt?: number; }
🔗 Provider Adapter 规范
每个 LLM Provider 必须实现一个 Adapter,负责 Metona IR 和外部 API 格式之间的双向转换。
export interface IMetonaProviderAdapter { /** Provider 标识 */ readonly providerId: string; /** 支持的模型列表 */ readonly supportedModels: string[]; /** 上下文窗口大小 */ getContextWindow(model: string): number; /** 健康检查 */ healthCheck(): Promise<boolean>; /** * 核心方法:发送请求 * @param request - Metona 标准请求 * @returns Metona 标准响应 */ send(request: MetonaRequest): Promise<MetonaResponse>; /** * 核心方法:发送流式请求 * @param request - Metona 标准请求 * @param onEvent - 流式事件回调 * @returns 完整响应(流结束后返回) */ sendStream( request: MetonaRequest, onEvent: (event: MetonaStreamEvent) => void ): Promise<MetonaResponse>; /** 获取可用模型列表 */ listModels(): Promise<MetonaModelInfo[]>; } export interface MetonaModelInfo { id: string; providerId: string; displayName: string; contextWindow: number; maxOutput: number; supportsStreaming: boolean; supportsThinking: boolean; supportsImages: boolean; supportsToolCalling: boolean; supportsStructuredOutput: boolean; pricing?: MetonaPricing; } export interface MetonaPricing { inputPerMillion: number; outputPerMillion: number; currency: string; }
Adapter 实现清单
| 适配器 | providerId | 目标 API | 传输格式 |
|---|---|---|---|
| DeepSeekAdapter | deepseek | https://api.deepseek.com/chat/completions | OpenAI 兼容 JSON |
| AgnesAdapter | agnes-ai | https://apihub.agnes-ai.com/v1/chat/completions | OpenAI 兼容 JSON |
| OllamaAdapter | ollama | http://localhost:11434/api/chat | Ollama 原生 JSON / NDJSON |
| AnthropicAdapter(未来计划,未实现) | anthropic | https://api.anthropic.com/v1/messages | Anthropic 原生 JSON |
| OpenAIAdapter(未来计划,未实现) | openai | https://api.openai.com/v1/chat/completions | OpenAI 原生 JSON |
Thinking 模式 Provider 映射表
各 Provider 的思考模式控制方式不同,Adapter 负责将 MetonaGenerationParams.thinkingEffort 映射为目标 Provider 的原生参数:
| Provider | 开启方式 | thinkingEffort 映射 | 回传规则 |
|---|---|---|---|
| DeepSeek | thinking: {type: "enabled"} + reasoning_effort |
low/medium → "high", high → "high", max → "max" |
工具调用轮次的 reasoning_content 必须回传上下文 |
| Agnes (OpenAI 兼容) | chat_template_kwargs: {enable_thinking: true} |
low → false, medium/high/max → true |
不强制回传 |
| Agnes (Anthropic 兼容) | thinking: {type: "enabled", budget_tokens: N} |
low → 1024, medium → 2048, high → 4096, max → 8192 |
不强制回传 |
| Ollama | think: true/false 或 "high"/"medium"/"low" |
low → "low", medium → "medium", high → "high", max → true |
message.thinking 字段,不强制回传 |
• DeepSeek/Agnes(OpenAI):
reasoning_content → MetonaResponse.reasoningContent
• Agnes(Anthropic):
thinking block → MetonaResponse.reasoningContent
• Ollama:
message.thinking → MetonaResponse.reasoningContent
流式模式下,思考内容增量统一映射为
reasoning_delta 事件。
MVP 优先级
当前优先实现以下 3 个 Adapter,Anthropic 和 OpenAI 为未来计划:
| 优先级 | Adapter | 状态 |
|---|---|---|
| P0 | DeepSeekAdapter | MVP 必须实现 |
| P0 | AgnesAdapter | MVP 必须实现 |
| P1 | OllamaAdapter | MVP 必须实现(本地推理) |
| P2 | AnthropicAdapter | 未来计划 |
| P2 | OpenAIAdapter | 未来计划 |
Provider 故障转移策略
当主 Provider 不可用时,Agent Loop 按以下策略处理:
| 步骤 | 条件 | 动作 |
|---|---|---|
| 1. 重试 | MetonaError.retryable === true | 按 retryAfterMs 等待后重试(最多 3 次) |
| 2. 故障转移 | 重试仍失败 + 已配置 fallback Provider | 切换到备选 Provider 重新发送请求 |
| 3. 通知用户 | 故障转移触发时 | 通过 IPC 推送 agent:providerSwitched 事件,UI 显示 Toast 提示 |
| 4. 报错 | 无 fallback 或 fallback 也失败 | 返回 MetonaError,UI 显示错误 + 重试按钮 |
app_config 表中增加以下配置:
•
llm.fallbackProvider(string,备选 Provider ID)
•
llm.fallbackModel(string,备选模型名)
•
llm.fallbackApiKey(string,备选 API Key,加密存储)
用户可在设置界面配置备选 Provider。未配置时跳过故障转移步骤。
📖 完整示例
示例 1:普通文本对话(非流式)
用户发送问题,Agent Loop 构造请求,获得非流式回答。
// ===== Agent Loop 构造 ===== const request: MetonaRequest = { meta: { sessionId: 's_abc', iteration: 1, requestId: 'r_001', timestamp: Date.now(), agentVersion: '1.0.0' }, systemPrompt: { roleDefinition: '你是一个专业的编程助手。', outputConstraints: '用中文回答,代码块使用 ``` 包裹。', safetyGuidelines: '不编造事实,不确定时如实说明。', }, messages: [ { role: 'user', content: '解释 JavaScript 的事件循环机制。', timestamp: Date.now() }, ], params: { maxTokens: 4096, temperature: 0.0, stream: false, thinkingEnabled: false }, }; // ===== Adapter 返回 ===== const response: MetonaResponse = { meta: { requestId: 'r_001', provider: 'deepseek', model: 'deepseek-v4-pro', latencyMs: 850, timestamp: Date.now() }, content: 'JavaScript 的事件循环(Event Loop)是...\\n\\n```js\\nconsole.log(1)...\\n```', usage: { inputTokens: 120, outputTokens: 350, totalTokens: 470 }, finishReason: MetonaFinishReason.STOP, };
示例 2:ReAct 工具调用(流式)
用户请求需要工具调用,Agent Loop 迭代两次。
// ===== 第 1 轮:Agent 发送请求,LLM 返回工具调用(流式) ===== const request1: MetonaRequest = { meta: { sessionId: 's_xyz', iteration: 1, requestId: 'r_002', timestamp: Date.now(), agentVersion: '1.0.0' }, systemPrompt: { /* ... */ }, messages: [ { role: 'user', content: '读取 /home/user/data.csv 并分析内容。', timestamp: Date.now() }, ], tools: [ { name: 'read_file', description: '读取文件内容', /* ... */ }, { name: 'analyze_csv', description: '分析 CSV 数据', /* ... */ }, ], params: { stream: true, thinkingEnabled: true }, }; // 流式事件: // → thinking_start → reasoning_delta ... → thinking_end // → tool_call_complete { id: 'tc_1', name: 'read_file', args: { file_path: '/home/user/data.csv' } } // → done // ===== Agent Loop 执行工具后,构造第 2 轮请求 ===== const request2: MetonaRequest = { meta: { /* ... */, iteration: 2, requestId: 'r_003' }, // 消息包含历史 + 工具结果 messages: [ { role: 'user', content: '读取 /home/user/data.csv 并分析内容。', timestamp: t }, { role: 'assistant', content: '', toolCalls: [{ id: 'tc_1', name: 'read_file', args: { file_path: '/home/user/data.csv' }, iteration: 1 }], timestamp: t }, { role: 'tool', content: '', toolResult: { toolCallId: 'tc_1', toolName: 'read_file', result: '...', success: true, durationMs: 5, timestamp: t, summary: '文件 data.csv 包含 1000 行数据,字段为 name,age,email,city' }, timestamp: t }, ], tools: [/* 同上 */], params: { stream: true, thinkingEnabled: false }, };
🔄 迁移指南
将现有代码迁移到 Metona IR 标准的步骤。
文件结构
electron/harness/types/ ├── metona-request.ts // MetonaRequest, MetonaMessage, MetonaToolDef 等 ├── metona-response.ts // MetonaResponse, MetonaStreamEvent, MetonaError 等 ├── metona-context.ts // MetonaContext, MetonaSystemPrompt ├── metona-memory.ts // MetonaMemoryItem ├── metona-tool.ts // MetonaToolCall, MetonaToolResult ├── metona-adapter.ts // IMetonaProviderAdapter 接口 └── index.ts // 统一导出 electron/harness/adapters/ ├── base-adapter.ts // Adapter 基类(共享逻辑) ├── deepseek.adapter.ts ├── agnes-ai.adapter.ts ├── ollama.adapter.ts # ├── anthropic.adapter.ts # 未来计划 # └── openai.adapter.ts # 未来计划
迁移检查清单
| # | 检查项 | 涉及文件 |
|---|---|---|
| 1 | Agent Loop Engine 只引用 MetonaRequest / MetonaResponse | agent-loop/engine.ts |
| 2 | Context Builder 输出 MetonaContext | harness/prompts/ |
| 3 | Tool Registry 使用 MetonaToolDef / MetonaToolCall / MetonaToolResult | harness/tools/ |
| 4 | Memory Manager 使用 MetonaMemoryItem | harness/memory/ |
| 5 | IPC Preload 桥接层只传递 Metona IR 类型 | electron/preload.ts |
| 6 | IPC Handlers 的输入输出为 Metona IR 类型 | electron/ipc/*.handlers.ts |
| 7 | React 组件/Zustand Store 只读写 Metona IR 类型 | src/stores/, src/components/ |
| 8 | 每个 Provider Adapter 实现 IMetonaProviderAdapter | harness/adapters/ |
| 9 | 流式事件通过 MetonaStreamEvent 推送 | hooks/useAgentStream.ts |
| 10 | 所有外部 API 原生类型不出现在 harness/ 和 src/ 中 | 全局 |
📜 Metona 内部 API 请求与响应标准 —— 项目端到端类型安全的基础
版本: v1.0.0 · 生效日期: 2026-07-15
所属项目: Metona (AI Agent Desktop) · 技术栈: TypeScript + React + SQLite + Electron